package hust.rpc.utils;

import hust.rpc.annotation.Service;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;

@Slf4j
public class ServiceScanUtils {

    public static final String FILE = "file";

    /**
     * 依据包名扫描获取接口集合
     *
     * @param packageName 包名
     * @return Set<Class < ?>> 接口集合
     */
    public static Set<Class<?>> scanService(String packageName, Predicate<Class<?>> filter) throws IOException, ClassNotFoundException {
        Set<Class<?>> classes = new LinkedHashSet<>();
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs;
        // 获取包下的文件资源集合
        dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        // 迭代文件资源
        while (dirs.hasMoreElements()) {
            // 获取文件资源
            URL url = dirs.nextElement();
            // 获取协议名
            String protocol = url.getProtocol();
            // 如果是文件协议
            if (FILE.equals(protocol)) {
                // 获取包路径
                String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8.name());
                // 递归扫描包下的所有文件,并添加到集合中
                findServices(packageName, filePath, true, classes);
            }
        }
        filterService(classes, filter);
        return classes;
    }

    /**
     * 筛选服务
     *
     * @param services 服务集合
     */
    private static void filterService(Set<Class<?>> services, Predicate<Class<?>> filter) {
        // 排除非@Service注解注释的类
        services.removeIf(service -> {
            if (filter == null) {
                return !service.isAnnotationPresent(Service.class);
            }
            return !service.isAnnotationPresent(Service.class) || !filter.test(service);
        });
    }

    /**
     * 通过包名获取包下的所有服务实现类集合
     *
     * @param packageName 包名
     * @param packagePath 包路径
     * @param recursive   是否递归
     * @param classes     服务实现类集合
     */
    private static void findServices(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) throws ClassNotFoundException {
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 若目录不存在 || 不为目录,直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        // 过滤文件(目录或.class文件)
        File[] files = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
        if (files == null || files.length == 0) return;

        // 遍历文件
        for (File file : files) {
            // 如果是目录,递归扫描
            if (file.isDirectory()) {
                findServices(packageName + "." + file.getName(),
                        file.getAbsolutePath(), recursive, classes);
            } else {
                // 如果是java类文件,去除.class文件后缀
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 加入集合
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    log.error("服务实现类文件加载失败", e);
                    throw e;
                }
            }
        }
    }
}
